package com.ming_za.od_updater_sdk;

import com.ming_za.od_updater_sdk.server_api.DefaultUpdaterAPI;
import com.ming_za.od_updater_sdk.server_api.ServerAPIState;
import com.ming_za.od_updater_sdk.server_api.IUpdaterServer;
import com.ming_za.od_updater_sdk.log.BasisLogger;
import com.ming_za.od_updater_sdk.exception.UpdaterConfigException;
import com.ming_za.od_updater_sdk.handler.DefaultUpdaterHandler;
import com.ming_za.od_updater_sdk.handler.IUpdateHandler;
import com.ming_za.od_updater_sdk.event.*;
import com.ming_za.od_updater_sdk.exception.UnConnectServerException;
import com.ming_za.od_updater_sdk.obj.ReqValue;
import com.ming_za.od_updater_sdk.obj.Res;
import com.ming_za.od_updater_sdk.obj.UpdateError;
import com.ming_za.od_updater_sdk.obj.UpdateRecord;
import com.ming_za.od_updater_sdk.processing.record.DefaultUpdateRecorder;
import com.ming_za.od_updater_sdk.processing.record.IUpdateRecorder;
import com.ming_za.od_updater_sdk.processing.search.IResSearchProcessor;
import com.ming_za.od_updater_sdk.processing.search.IntelligentSearchProcessor;
import com.ming_za.od_updater_sdk.processing.update.UpdateTaskRunnable;
import com.ming_za.od_updater_sdk.transmit.BasisStreamTransmit;
import com.ming_za.od_updater_sdk.processing.file.DefaultFileProcessor;
import com.ming_za.od_updater_sdk.processing.file.IFileProcessor;
import com.ming_za.od_updater_sdk.transmit.IStreamTransmit;
import com.ming_za.od_updater_sdk.utils.FileUtils;
import com.ming_za.od_updater_sdk.utils.VersionUtils;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 初梦更新器-第二代-sdk
 * <p>主要使用http/https协议实现功能</p>
 * <pre>
 * JAVA版本：1.8
 * sdk版本：1.0
 * 其他依赖：okhttp3-4.11.0,gson-2.8.9
 * 开源协议：LGPL
 * 作者：ming_za
 * QQ：2510674200
 * </pre>
 * <p>本类为更新程序入口</p>
 */
public class ODUpdater implements IUpdater{

    /**
     * 更新器服务器ip
     */
    private String ip;
    /**
     * 更新器未发布版本密钥,不使用时设为0即可
     */
    private int unpublishKey;

    /**
     * 标签id
     */
    private long labelId;

    /**
     * 请求协议
     */
    private String reqProtocol = "http";

    /**
     * 更新生命周期监听回调
     */
    private IUpdateHandler IUpdateHandler;

    /**
     * 资源检索处理器
     */
    private IResSearchProcessor IResSearchProcessor;

    /**
     * 更新服务器API
     */
    private IUpdaterServer IUpdaterServer;

    /**
     * 文件处理器
     */
    private IFileProcessor IFileProcessor;

    /**
     * 文件工具
     */
    private FileUtils fileUtils;

    /**
     * 流传输接口
     */
    private IStreamTransmit IStreamTransmit;

    /**
     * 更新记录器
     */
    private IUpdateRecorder updateRecorder;

    /**
     * 更新资源的根目录
     */
    private String rootDirectory;

    /**
     * 日志
     */
    private BasisLogger logger;

    /**
     * 当前更新错误列表
     */
    private final List<UpdateError> errorList = new ArrayList<>();

    /**
     * 生产者线程
     */
    Thread producerThread = null;

    /**
     * 请使用{@link #createUpdater}创建
     */
    public ODUpdater(String ip, int unpublishKey, long labelId,
                      IUpdateHandler IUpdateHandler, IResSearchProcessor IResSearchProcessor,
                      IUpdaterServer IUpdaterServer, String rootDirectory, IFileProcessor IFileProcessor,
                      IStreamTransmit IStreamTransmit, int maxBufferSize, IUpdateRecorder updateRecorder, BasisLogger logger) {
        this.ip = ip;
        this.unpublishKey = unpublishKey;
        this.labelId = labelId;
        this.IUpdateHandler = IUpdateHandler;
        this.IResSearchProcessor = IResSearchProcessor;
        this.IUpdaterServer = IUpdaterServer;
        this.rootDirectory = rootDirectory;
        this.IFileProcessor = IFileProcessor;
        this.updateRecorder = updateRecorder;
        this.logger = logger;
        this.IStreamTransmit = IStreamTransmit;
        this.fileUtils = new FileUtils(maxBufferSize);
    }

    /**
     * 创建更新器
     * @param ip 更新服务器ip
     * @param unpublishKey 未发布版本密钥
     * @param labelId 资源标签id
     * @return 更新器实例
     */
    public static ODUpdater createUpdater(String ip,long labelId, int unpublishKey,String rootDirectory){
        return new ODUpdater(ip,unpublishKey,labelId,
                new DefaultUpdaterHandler(),new IntelligentSearchProcessor(),
                new DefaultUpdaterAPI(),rootDirectory,
                new DefaultFileProcessor(),new BasisStreamTransmit(),
                1024,new DefaultUpdateRecorder(),new BasisLogger());
    }

    /**
     * 创建更新器
     * @param ip 更新服务器ip
     * @param labelId 资源标签id
     * @return 更新器实例
     */
    public static ODUpdater createUpdater(String ip, long labelId){
        return createUpdater(ip,labelId,"./");
    }

    public static ODUpdater createUpdater(String ip, long labelId,String rootDirectory){
        return createUpdater(ip,labelId,0,rootDirectory);
    }

    public ODUpdater setUpdateHandler(com.ming_za.od_updater_sdk.handler.IUpdateHandler IUpdateHandler) {
        if (isStart){
            throw new UpdaterConfigException("无法在更新器运行时更改配置！");
        }
        this.IUpdateHandler = IUpdateHandler;
        return this;
    }

    public ODUpdater setResSearchProcessor(com.ming_za.od_updater_sdk.processing.search.IResSearchProcessor IResSearchProcessor) {
        if (isStart){
            throw new UpdaterConfigException("无法在更新器运行时更改配置！");
        }
        this.IResSearchProcessor = IResSearchProcessor;
        return this;
    }

    public ODUpdater setUpdaterServer(com.ming_za.od_updater_sdk.server_api.IUpdaterServer IUpdaterServer) {
        if (isStart){
            throw new UpdaterConfigException("无法在更新器运行时更改配置！");
        }
        this.IUpdaterServer = IUpdaterServer;
        return this;
    }

    public ODUpdater setFileProcessor(com.ming_za.od_updater_sdk.processing.file.IFileProcessor IFileProcessor) {
        if (isStart){
            throw new UpdaterConfigException("无法在更新器运行时更改配置！");
        }
        this.IFileProcessor = IFileProcessor;
        return this;
    }

    public ODUpdater setFileUtils(FileUtils fileUtils) {
        if (isStart){
            throw new UpdaterConfigException("无法在更新器运行时更改配置！");
        }
        this.fileUtils = fileUtils;
        return this;
    }

    public ODUpdater setUpdateRecorder(IUpdateRecorder updateRecorder) {
        if (isStart){
            throw new UpdaterConfigException("无法在更新器运行时更改配置！");
        }
        this.updateRecorder = updateRecorder;
        return this;
    }

    public ODUpdater setRootDirectory(String rootDirectory) {
        if (isStart){
            throw new UpdaterConfigException("无法在更新器运行时更改配置！");
        }
        this.rootDirectory = rootDirectory;
        return this;
    }

    public ODUpdater setLogger(BasisLogger logger) {
        if (isStart){
            throw new UpdaterConfigException("无法在更新器运行时更改配置！");
        }
        this.logger = logger;
        return this;
    }

    public ODUpdater setReqProtocol(String reqProtocol) {
        if (isStart){
            throw new UpdaterConfigException("无法在更新器运行时更改配置！");
        }
        this.reqProtocol = reqProtocol;
        return this;
    }

    /**
     * 更新池
     * <p>更新任务消费者线程</p>
     */
    private ThreadPoolExecutor consumerThreadPool;

    private final Lock lock = new ReentrantLock();
    private final Condition condition = lock.newCondition();

    /**
     * 更新开始标识
     */
    private boolean isStart = false;

    /**
     * 更新停止标识
     */
    private boolean stop = true;

    /**
     * 更新任务结束数量
     */
    private final AtomicInteger endUpdateCount = new AtomicInteger(0);

    /**
     * 开启更新
     * <pre>
     *     逐个版本更新，并发下载只在同一个版本多个资源中有效。
     *     每个版本需要等上一个版本更新完成时再更新，避免意外断开导致资源混乱。
     * </pre>
     * <pre>
     *     异常处理：
     *      当某个更新出现异常时，暂停新的更新产生。
     *      更新池的所有更新结束时直接跳出版本循环，不调用{@link IUpdateHandler#updateDoneVersion(int)}
     *      最后将错误信息传递给{@link IUpdateHandler#allUpdateComplete(UpdateCompleteEvent)} ，
     *      可通过调用{@link UpdateCompleteEvent#hasError()}判断是否出现异常。
     * </pre>
     * @return 存在更新，而且更新启动时返回true，不存在更新则返回false
     */
    public boolean startUpdate() throws RuntimeException{
        try {
            return update();
        }catch (Exception e){
            logger.error(e.getMessage(),e);
            throw new RuntimeException(e);
        }
    }

    private boolean update(){

        if (isStart){
            throw new IllegalStateException("更新程序已经在运行中");
        }

        logger.info("更新初始化...");

        endUpdateCount.set(0);
        errorList.clear();
        isStart = true;
        stop = false;

        logger.info("加载本地当前资源版本");
        UpdateRecord updateRecord = updateRecorder.getUpdatedVersion(this);

        logger.debug("当前版本：" + VersionUtils.intToVersion(updateRecord.getVersion()) + " | 未更新完成版本：" + VersionUtils.intToVersion(updateRecord.getNotCompletedVersion()));

        //从小到大排序
        //Map<版本号,资源列表>
        Map<Integer, List<Res>> needUpdate = new TreeMap<>(Comparator.naturalOrder());

        //通过资源检索处理器获取需要更新的资源
        //将资源放入 needUpdate 中
        logger.info("开始检索需要更新的资源");
        IResSearchProcessor.getUpdateRes(needUpdate,this,updateRecord);

        if (needUpdate.isEmpty()){
            return false;
        }

        if (logger.isDebug()){
            for (Map.Entry<Integer, List<Res>> entry : needUpdate.entrySet()){
                logger.debug("================[v" + VersionUtils.intToVersion(entry.getKey()) + "]================");
                for (Res res : entry.getValue()){
                    logger.debug("资源-->id：" + res.getId() + " | 是否为删除类型：" + res.isDelete() + " | 路径：" + res.getResPath());
                }
            }
        }

        logger.debug("获取最大并发下载数量");
        ReqValue<Integer> reqConcurrencyCount = IUpdaterServer.getConcurrencyCount(this);
        if (reqConcurrencyCount.getServerAPIState() != ServerAPIState.SUCCESS){
            throw new UnConnectServerException("无法连接至服务器");
        }
        int concurrencyCount = reqConcurrencyCount.getValue();
        logger.debug("并发下载数量->" + concurrencyCount);

        BlockingQueue<Runnable> consumerQueue = new LinkedBlockingQueue<>(5);

        ThreadFactory daemonThreadFactory = r -> {
            Thread thread = new Thread(r);
            thread.setDaemon(true); // 设置为守护线程
            return thread;
        };

        consumerThreadPool = new ThreadPoolExecutor(concurrencyCount,concurrencyCount,1, TimeUnit.MINUTES,consumerQueue,daemonThreadFactory);

        //发布更新开始前事件
        UpdateStartEvent updateStartEvent = new UpdateStartEvent(needUpdate);
        IUpdateHandler.updateStart(updateStartEvent,this);
        if (updateStartEvent.isCancel()){
            //事件取消
            logger.info("取消更新");
            return false;
        }

        consumerThreadPool.prestartAllCoreThreads();

        //启动生产者线程，遍历创建更新任务
        producerThread = new Thread(() -> {

            logger.info("=========================初始化完毕！更新启动！=========================");
            logger.debug("生产者线程启动，正在创建更新任务...");

            int nowVersion = 0;

            //遍历需要更新的资源
            for (Map.Entry<Integer, List<Res>> entry : needUpdate.entrySet()) {
                List<Res> resList = entry.getValue();
                nowVersion = entry.getKey();
                IUpdateHandler.updateStartVersion(nowVersion);

                if (logger.isEnabled()){
                    logger.info("正在更新版本------------>[v" + VersionUtils.intToVersion(nowVersion) + "]<------------");
                }

                //记录当前版本更新进度
                int endIndex = endUpdateCount.get() + resList.size();

                for (Res res : resList) {

                    PreUpdateEvent preUpdateEvent = new PreUpdateEvent(res,nowVersion);
                    IUpdateHandler.preUpdate(preUpdateEvent);
                    if (preUpdateEvent.isCancel()) {
                        logger.info("取消资源更新-->id：" + res.getId() + " | 是否为删除类型：" + res.isDelete() + " | 路径：" + res.getResPath());
                        //取消更新
                        continue;
                    }

                    //并发更新，线程池满时阻塞，优化内存
                    try {
                        consumerQueue.put(new UpdateTaskRunnable(this, res,nowVersion));
                    } catch (InterruptedException e) {
                        errorList.add(new UpdateError(res,e));
                    }

                    //出错后跳出循环
                    if (!errorList.isEmpty() || stop){
                        break;
                    }
                }

                //等待当前版本所有任务完成
                if (errorList.isEmpty() && !stop && endUpdateCount.get() < endIndex){
                    synchronized (endUpdateCount){
                        while (endUpdateCount.get() < endIndex && !stop){
                            logger.debug("等待当前版本所有更新结束... " + endUpdateCount.get()  + "/" + endIndex);
                            try {
                                endUpdateCount.wait();
                            } catch (InterruptedException e) {
                                break;
                            }
                        }
                    }
                }

                //出错后跳出循环
                if (!errorList.isEmpty() || stop){
                    consumerThreadPool.shutdownNow();
                    break;
                }

                logger.info("版本更新完成------------>[v" + VersionUtils.intToVersion(nowVersion) + "]<------------");
                IUpdateHandler.updateDoneVersion(nowVersion);
            }

            if (!consumerThreadPool.isShutdown()){
                consumerThreadPool.shutdown();
            }

            logger.info("=====================================================================");
            logger.info("更新完成！异常数量->" + errorList.size());
            isStart = false;
            if (!stop){
                stop = true;
            }

            //所有任务结束
            IUpdateHandler.allUpdateComplete(new UpdateCompleteEvent(errorList.isEmpty() ? 0 : nowVersion, errorList,endUpdateCount.get()));
        });

        producerThread.start();
        return true;
    }

    public void addEndTask(){
        lock.lock();
        synchronized (endUpdateCount){
            endUpdateCount.incrementAndGet();
            endUpdateCount.notify(); //唤醒生产者线程
        }
        condition.signalAll();
        lock.unlock();
    }

    public synchronized void reducePoolSize(){
        if (consumerThreadPool.getCorePoolSize() > 1){
            consumerThreadPool.setCorePoolSize(consumerThreadPool.getCorePoolSize() - 1);
        }
    }

    @Override
    public String getIp() {
        return ip;
    }

    public void setIp(String ip) {
        this.ip = ip;
    }

    @Override
    public int getUnpublishKey() {
        return unpublishKey;
    }

    public void setUnpublishKey(int unpublishKey) {
        this.unpublishKey = unpublishKey;
    }

    @Override
    public long getLabelId() {
        return labelId;
    }

    @Override
    public void setLabelId(long labelId) {
        this.labelId = labelId;
    }

    @Override
    public IUpdateHandler getUpdateHandler() {
        return IUpdateHandler;
    }

    @Override
    public IResSearchProcessor getResSearchProcessor() {
        return IResSearchProcessor;
    }

    @Override
    public IUpdaterServer getUpdaterServerAPI() {
        return IUpdaterServer;
    }

    @Override
    public IFileProcessor getFileProcessor() {
        return IFileProcessor;
    }

    @Override
    public FileUtils getFileUtils() {
        return fileUtils;
    }

    @Override
    public String getRootDirectory() {
        return rootDirectory;
    }

    @Override
    public void addUpdateError(UpdateError error){
        errorList.add(error);
    }

    @Override
    public void clearUpdateError(){
        errorList.clear();
    }

    @Override
    public IUpdateRecorder getUpdateRecorder() {
        return updateRecorder;
    }

    @Override
    public BasisLogger getLogger() {
        return logger;
    }

    @Override
    public String getReqProtocol() {
        return reqProtocol;
    }

    @Override
    public AtomicInteger getEndUpdateCount() {
        return endUpdateCount;
    }

    @Override
    public Lock getLock() {
        return lock;
    }

    @Override
    public Condition getCondition() {
        return condition;
    }

    /**
     * 终止更新
     */
    @Override
    public void shutdown(){
        stop = true;
        if (consumerThreadPool != null && !consumerThreadPool.isShutdown()){
            consumerThreadPool.shutdownNow();
        }
        logger.debug("更新终止！");
    }

    @Override
    public void awaitUpdateDone() throws InterruptedException {
        if (producerThread != null){
            producerThread.join();
        }
    }

    @Override
    public IStreamTransmit getIStreamTransmit() {
        return IStreamTransmit;
    }

    public void setIStreamTransmit(IStreamTransmit IStreamTransmit) {
        this.IStreamTransmit = IStreamTransmit;
    }
}
